'use strict';
var inherit = require('./utils').inherit;
var type = require('./utils').type;
var Facade = require('./facade');
var Identify = require('./identify');
var isEmail = require('is-email');
var get = require('obj-case');
/**
* Initialize a new `Track` facade with a `dictionary` of arguments.
*
* @param {object} dictionary
* @property {string} event
* @property {string} userId
* @property {string} sessionId
* @property {Object} properties
* @property {Object} options
* @param {Object} opts
* @property {boolean|undefined} clone
*/
function Track(dictionary, opts) {
Facade.call(this, dictionary, opts);
}
/**
* Inherit from `Facade`.
*/
inherit(Track, Facade);
/**
* Return the facade's action.
*
* @return {string}
*/
Track.prototype.action = function() {
return 'track';
};
Track.prototype.type = Track.prototype.action;
/**
* Setup some basic proxies.
*/
Track.prototype.event = Facade.field('event');
Track.prototype.value = Facade.proxy('properties.value');
/**
* Misc
*/
Track.prototype.category = Facade.proxy('properties.category');
/**
* Ecommerce
*/
/**
* ids
*/
Track.prototype.id = Facade.proxy('properties.id');
Track.prototype.productId = function() {
return this.proxy('properties.product_id') || this.proxy('properties.productId');
};
Track.prototype.promotionId = function() {
return this.proxy('properties.promotion_id') || this.proxy('properties.promotionId');
};
Track.prototype.cartId = function() {
return this.proxy('properties.cart_id') || this.proxy('properties.cartId');
};
Track.prototype.checkoutId = function() {
return this.proxy('properties.checkout_id') || this.proxy('properties.checkoutId');
};
Track.prototype.paymentId = function() {
return this.proxy('properties.payment_id') || this.proxy('properties.paymentId');
};
Track.prototype.couponId = function() {
return this.proxy('properties.coupon_id') || this.proxy('properties.couponId');
};
Track.prototype.wishlistId = function() {
return this.proxy('properties.wishlist_id') || this.proxy('properties.wishlistId');
};
Track.prototype.reviewId = function() {
return this.proxy('properties.review_id') || this.proxy('properties.reviewId');
};
Track.prototype.orderId = function() {
// doesn't follow above convention since this fallback order was how it used to be
return this.proxy('properties.id')
|| this.proxy('properties.order_id')
|| this.proxy('properties.orderId');
};
Track.prototype.sku = Facade.proxy('properties.sku');
Track.prototype.tax = Facade.proxy('properties.tax');
Track.prototype.name = Facade.proxy('properties.name');
Track.prototype.price = Facade.proxy('properties.price');
Track.prototype.total = Facade.proxy('properties.total');
Track.prototype.repeat = Facade.proxy('properties.repeat');
Track.prototype.coupon = Facade.proxy('properties.coupon');
Track.prototype.shipping = Facade.proxy('properties.shipping');
Track.prototype.discount = Facade.proxy('properties.discount');
Track.prototype.shippingMethod = function() {
return this.proxy('properties.shipping_method') || this.proxy('properties.shippingMethod');
};
Track.prototype.paymentMethod = function() {
return this.proxy('properties.payment_method') || this.proxy('properties.paymentMethod');
};
/**
* Description
*/
Track.prototype.description = Facade.proxy('properties.description');
/**
* Plan
*/
Track.prototype.plan = Facade.proxy('properties.plan');
/**
* Get subtotal.
*
* @return {number}
*/
Track.prototype.subtotal = function() {
var subtotal = get(this.properties(), 'subtotal');
var total = this.total() || this.revenue();
if (subtotal) return subtotal;
Iif (!total) return 0;
if (this.total()) {
var n = this.tax();
Eif (n) total -= n;
n = this.shipping();
Eif (n) total -= n;
n = this.discount();
if (n) total += n;
}
return total;
};
/**
* Get products.
*
* @return {Array}
*/
Track.prototype.products = function() {
var props = this.properties();
var products = get(props, 'products');
return type(products) === 'array' ? products : [];
};
/**
* Get quantity.
*
* @return {number}
*/
Track.prototype.quantity = function() {
var props = this.obj.properties || {};
return props.quantity || 1;
};
/**
* Get currency.
*
* @return {string}
*/
Track.prototype.currency = function() {
var props = this.obj.properties || {};
return props.currency || 'USD';
};
/**
* BACKWARDS COMPATIBILITY: should probably re-examine where these come from.
*/
Track.prototype.referrer = function() {
return this.proxy('context.referrer.url')
|| this.proxy('context.page.referrer')
|| this.proxy('properties.referrer');
};
Track.prototype.query = Facade.proxy('options.query');
/**
* Get the call's properties.
*
* @param {Object} aliases
* @return {Object}
*/
Track.prototype.properties = function(aliases) {
var ret = this.field('properties') || {};
aliases = aliases || {};
for (var alias in aliases) {
var value = this[alias] == null ? this.proxy('properties.' + alias) : this[alias]();
Iif (value == null) continue;
ret[aliases[alias]] = value;
delete ret[alias];
}
return ret;
};
/**
* Get the call's username.
*
* @return {string|undefined}
*/
Track.prototype.username = function() {
return this.proxy('traits.username')
|| this.proxy('properties.username')
|| this.userId()
|| this.sessionId();
};
/**
* Get the call's email, using an the user ID if it's a valid email.
*
* @return {string|undefined}
*/
Track.prototype.email = function() {
var email = this.proxy('traits.email')
|| this.proxy('properties.email')
|| this.proxy('options.traits.email');
if (email) return email;
var userId = this.userId();
Eif (isEmail(userId)) return userId;
};
/**
* Get the call's revenue, parsing it from a string with an optional leading
* dollar sign.
*
* For products/services that don't have shipping and are not directly taxed,
* they only care about tracking `revenue`. These are things like
* SaaS companies, who sell monthly subscriptions. The subscriptions aren't
* taxed directly, and since it's a digital product, it has no shipping.
*
* The only case where there's a difference between `revenue` and `total`
* (in the context of analytics) is on ecommerce platforms, where they want
* the `revenue` function to actually return the `total` (which includes
* tax and shipping, total = subtotal + tax + shipping). This is probably
* because on their backend they assume tax and shipping has been applied to
* the value, and so can get the revenue on their own.
*
* @return {number}
*/
Track.prototype.revenue = function() {
var revenue = this.proxy('properties.revenue');
var event = this.event();
var orderCompletedRegExp = /^[ _]?completed[ _]?order[ _]?|^[ _]?order[ _]?completed[ _]?$/i;
// it's always revenue, unless it's called during an order completion.
if (!revenue && event && event.match(orderCompletedRegExp)) {
revenue = this.proxy('properties.total');
}
return currency(revenue);
};
/**
* Get cents.
*
* @return {number}
*/
Track.prototype.cents = function() {
var revenue = this.revenue();
return typeof revenue !== 'number' ? this.value() || 0 : revenue * 100;
};
/**
* A utility to turn the pieces of a track call into an identify. Used for
* integrations with super properties or rate limits.
*
* TODO: remove me.
*
* @return {Facade}
*/
Track.prototype.identify = function() {
var json = this.json();
json.traits = this.traits();
return new Identify(json, this.opts);
};
/**
* Get float from currency value.
*
* @param {*} val
* @return {number}
*/
function currency(val) {
if (!val) return;
if (typeof val === 'number') {
return val;
}
Iif (typeof val !== 'string') {
return;
}
val = val.replace(/\$/g, '');
val = parseFloat(val);
if (!isNaN(val)) {
return val;
}
}
/**
* Exports.
*/
module.exports = Track;
|